package com.jinly.ryder.monitor.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jinly.ryder.common.service.SettingItemService;
import com.jinly.ryder.manage.service.AppInfoService;
import com.jinly.ryder.manage.service.AppInstanceService;
import com.jinly.ryder.manage.service.model.AppInfo;
import com.jinly.ryder.manage.service.model.AppInstance;
import com.jinly.ryder.manage.service.model.AppMonitorType;
import com.jinly.ryder.manage.service.model.InstanceType;
import com.jinly.ryder.manage.service.model.setting.MonitorSetting;
import com.jinly.ryder.monitor.config.MonitorProperties;
import com.jinly.ryder.monitor.dao.MonitorDataMapper;
import com.jinly.ryder.monitor.dao.entity.MonitorDataDO;
import com.jinly.ryder.monitor.service.ActuatorQueryService;
import com.jinly.ryder.monitor.service.MonitorDataCollectService;
import com.jinly.ryder.monitor.service.mapping.MonitorDataMapping;
import com.jinly.ryder.monitor.service.model.ActuateMetric;
import com.jinly.ryder.monitor.service.model.MonitorData;
import com.jinly.ryder.monitor.service.model.MonitorKeyType;
import com.jinly.ryder.monitor.service.model.ServiceStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class MonitorDataCollectServiceImpl implements MonitorDataCollectService {
    private final AppInfoService appInfoService;
    private final SettingItemService settingItemService;
    private final AppInstanceService appInstanceService;
    private final ActuatorQueryService actuatorQueryService;

    private final MonitorDataMapper monitorDataMapper;
    private final MonitorDataMapping monitorDataMapping;

    private final MonitorProperties monitorProperties;

    @Override
    public void collectAll() {
        Long l1 = System.currentTimeMillis();
        List<AppInfo> appInfoList = appInfoService.allList();
        for (AppInfo appInfo : appInfoList) {
            collectByApp(appInfo);
        }
        Long l2 = System.currentTimeMillis();
        log.info("MonitorDataCollect cost:{}ms", (l2 - l1));
    }

    @Override
    public void cleanData(Integer maxDay, Integer batchSize) {
        Long l1 = System.currentTimeMillis();
        LocalDateTime maxDateTime = LocalDateTime.now().minusDays(maxDay).withHour(0).withMinute(0).withSecond(0).withNano(0);
        while (true) {
            Wrapper<MonitorDataDO> queryWrapper = new QueryWrapper<MonitorDataDO>().lambda()
                    .lt(MonitorDataDO::getCreated, maxDateTime)
                    .last("limit " + batchSize);
            if (monitorDataMapper.delete(queryWrapper) == 0) {
                break;
            }
        }
        Long l2 = System.currentTimeMillis();
        log.info("Monitor cleanData:{}ms", (l2 - l1));
    }

    private void collectByApp(AppInfo appInfo) {
        if (appInfo.getMonitorType() != AppMonitorType.SPRING_ACTUATOR) return;
        settingItemService.setSettingItem(appInfo);
        MonitorSetting monitorSetting = appInfo.getMonitorSetting();
        if (monitorSetting == null || !"web".equals(monitorSetting.getActuateType())) return;

        Long collectTime = LocalDateTime.now().withSecond(0).withNano(0).toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        List<AppInstance> instanceList = appInstanceService.list(appInfo.getAppName(), null);
        collectByByAppInstanceList(instanceList, monitorSetting, collectTime);

    }

    private void collectByByAppInstanceList(List<AppInstance> instanceList, MonitorSetting monitorSetting, Long collectTime) {
        if (instanceList != null && instanceList.size() > 0) {
            for (AppInstance appInstance : instanceList) {
                collectByByAppInstance(appInstance, monitorSetting, collectTime);
            }
        }
    }

    private void collectByByAppInstance(AppInstance instance, MonitorSetting monitorSetting, Long collectTime) {
        List<CompletableFuture<MonitorData>> futureList = new ArrayList<>();
        for (MonitorKeyType keyType : monitorProperties.getCollect().getKeys()) {
            futureList.add(CompletableFuture.supplyAsync(() -> queryMetrics(instance, keyType)));
        }
        futureList.add(CompletableFuture.supplyAsync(() -> queryHealth(instance)));
        if (monitorSetting.isServiceRegistry()) {
            futureList.add(CompletableFuture.supplyAsync(() -> queryServiceRegistry(instance)));
        }
        CompletableFuture.allOf(futureList.stream().toArray(CompletableFuture[]::new));
        List<MonitorData> dataList = new ArrayList<>();
        for (CompletableFuture<MonitorData> future : futureList) {
            try {
                MonitorData monitorData = future.get();
                if (monitorData != null) dataList.add(monitorData);
            } catch (Exception e) {
                log.error("future.get metrics error:{}", e.getMessage());
            }
        }
        if (dataList.size() > 0) {
            dataList.forEach(d -> d.setTime(collectTime));
            monitorDataMapper.batchInsert(monitorDataMapping.toEntityList(dataList));
        }
    }

    private MonitorData queryMetrics(AppInstance instance, MonitorKeyType keyType) {
        try {
            ActuateMetric metricResponse = actuatorQueryService.metrics(instance.getHost(), instance.getPort(), keyType.getKey());
            return newMonitorData(instance, metricResponse, keyType);
        } catch (Exception e) {
            log.error("queryMetrics,host[{}{}],key:{} error:{}", instance.getHost(), instance.getPort(), keyType.getKey(), e.getMessage());
            return null;
        }
    }

    private MonitorData queryHealth(AppInstance instance) {
        ServiceStatus serviceStatus = actuatorQueryService.health(instance.getHost(), instance.getPort());
        return newMonitorData(instance, serviceStatus);
    }

    private MonitorData queryServiceRegistry(AppInstance instance) {
        ServiceStatus serviceStatus = actuatorQueryService.serviceRegistry(instance.getHost(), instance.getPort());
        return newMonitorData(instance, serviceStatus);
    }

    private MonitorData newMonitorData(AppInstance instance, ServiceStatus serviceStatus) {
        MonitorData monitorData = new MonitorData();
        monitorData.setInstanceId(instance.getId());
        monitorData.setInstanceType(InstanceType.APP);
        monitorData.setKeyType(MonitorKeyType.HEALTH_STATUS);
        monitorData.setValue((serviceStatus != null && serviceStatus == ServiceStatus.UP) ? 1d : 0d);
        monitorData.setCycleUnit(TimeUnit.MINUTES);
        monitorData.setCycleValue(1);
        return monitorData;
    }

    private MonitorData newMonitorData(AppInstance instance, ActuateMetric metricResponse, MonitorKeyType keyType) {
        if (metricResponse != null && metricResponse.getMeasurements() != null) {
            Optional<ActuateMetric.Sample> optional = metricResponse.getMeasurements().stream().filter(s -> "VALUE".equals(s.getStatistic())).findFirst();
            if (optional.isPresent()) {
                ActuateMetric.Sample sample = optional.get();
                MonitorData monitorData = new MonitorData();
                monitorData.setInstanceId(instance.getId());
                monitorData.setInstanceType(InstanceType.APP);
                monitorData.setKeyType(keyType);
                monitorData.setValue(sample.getValue());
                monitorData.setCycleUnit(TimeUnit.MINUTES);
                monitorData.setCycleValue(1);
                return monitorData;
            }
        }
        return null;
    }
}
